Skip to content

feat(dashboard): create an event from the dashboard - #354

Merged
harshtandiya merged 10 commits into
developfrom
version-2-dashboard-create-event
Aug 25, 2026
Merged

feat(dashboard): create an event from the dashboard#354
harshtandiya merged 10 commits into
developfrom
version-2-dashboard-create-event

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Stacked on develop.

What changed

Creating an event needed Desk. This adds a create page at /manage/team/events/new,
reached from the Create Event button that has been inert on the team pages since they
shipped. It sits outside the manager shell, so the team comes from the route guard
rather than the layout.

  • buzz.api.events.create_event — membership decides who may call it; anything
    below Manager is refused, as is a non-member.
  • Schedule — start and end dates and times, plus a time zone picker searchable by
    city, country, everyday name or zone id.
  • Location — one control for both kinds: the team's venues, searched as you type
    with an inline add, or a Zoom meeting under Virtual.
  • Banner — the generated contour pattern, seeded by the title as you type. Opened
    up (wider spacing, 2px lines) since it moired at card size.

Worth a look during review:

  • Category and host are filled in by the server. Buzz Event requires both and the
    form asks for neither. Host is the team's own, minted on first use. Category is
    Meetups, or Zoom Meeting when Zoom was chosen — that one isn't cosmetic, it's what
    marks an event Zoom-backed (buzz/utils.py:13).
  • Zoom is booked automatically after insert, where Desk has a manual button. A
    failure leaves the event standing rather than rolling it back, since the call goes out
    to Zoom mid-request.
  • Time zone names come from the runtime, which still reports renamed zones under
    their old names (Asia/Calcutta). The eight a reader would notice are mapped forward;
    both spellings resolve the same in ZoneInfo.
  • fix(tests): stamp a team on the seeded test records rides along. before_tests
    seeds a venue, host and event without the team those doctypes made mandatory in the
    multi-tenancy work, so bench run-tests failed for every module before a single
    test ran. Nothing here could be tested without it. Happy to split it out.

Not done: no unsaved-changes guard, so Back discards a filled-in form silently; nothing
in the dashboard has been checked in dark mode; medium is derived server-side only, so
the picker no longer tracks it.

Demo

The create page, filled in — the banner pattern is seeded by the title as you type:

create-event

One control for both kinds of location, venues and Zoom under the same picker:

create-event-location

Time zone search, by city rather than by zone id:
create-event-timezone

If the user is Viewer for the team
create-event-viewer

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds dashboard event creation, including scheduling, venue creation, Zoom selection, generated banners, role-aware UI controls, and server-side event defaults. It also adds event-level venue ownership validation, but the cross-team disclosure fix remains incomplete for unstamped venues.

  • Adds a new dashboard event-creation page and supporting schedule, location, timezone, and banner utilities.
  • Adds a protected event-creation API that derives category, host, and delivery medium.
  • Adds Zoom meeting creation and test-fixture team assignments.
  • Adds venue-team validation, while retaining an ownership bypass for null or empty venue teams.

Confidence Score: 3/5

The PR is not yet safe to merge because the attempted venue-ownership fix still permits cross-team address disclosure through unstamped venues.

The new validator rejects venues belonging to a different populated team but deliberately accepts null or empty team values; the create API can therefore persist one of those venues on an event, and booking and calendar paths subsequently expose its address without another ownership check.

Files Needing Attention: buzz/events/doctype/buzz_event/buzz_event.py and buzz/api/events/services.py

Security Review

The venue ownership boundary remains bypassable for legacy venues with no team stamp. A manager can link such a venue to a new event, after which downstream booking and calendar paths expose its address without another authorization check.

Important Files Changed

Filename Overview
buzz/api/events/services.py Adds authorized event creation, derived host/category values, and optional Zoom booking; the caller-provided venue relies on Buzz Event validation for ownership enforcement.
buzz/events/doctype/buzz_event/buzz_event.py Adds venue-team validation, but accepts null or empty venue teams and therefore leaves the previously reported disclosure reachable.
dashboard/src/pages/manage/events/CreateEvent.vue Adds the event-creation form and correctly prevents roles below Manager from submitting through canCreate and canSave.
dashboard/src/composables/useTeamAccess.ts Adds a tri-state membership guard for pages outside the manager shell; role-specific creation is separately enforced by the page.
dashboard/src/router.ts Registers the full-bleed create-event route outside the manager shell, with access handled by the page guard.
dashboard/src/components/dashboard/events/EventLocation.vue Adds a unified venue and Zoom selector with inline venue creation.
dashboard/src/components/dashboard/events/EventSchedule.vue Adds event date, time, and timezone controls for the creation flow.

Fix all with Greploop Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
### Issue 1
buzz/events/doctype/buzz_event/buzz_event.py:115-118
**Unstamped venues bypass ownership**

When a manager supplies the name of a legacy venue whose `team` is null or empty, this truthiness check accepts the venue and links it to the manager's event, causing its address to be exposed through booking details, booking confirmation, and calendar invitations.

**How this was verified:** The create API copies the supplied venue after authorizing only the event team, while downstream booking and calendar paths dereference the accepted venue without another ownership check.

```suggestion
		venue_team = frappe.db.get_value("Event Venue", self.venue, "team")
		if venue_team != self.team:
			frappe.throw(_("Venue {0} belongs to another team.").format(self.venue))
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (8): Last reviewed commit: "feat(dashboard): keep an event from endi..." | Re-trigger Greptile

Comment thread buzz/api/events/services.py
Comment thread dashboard/src/composables/useTeamAccess.ts Outdated
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-events branch from 259edce to ff14afc Compare August 17, 2026 10:44
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-create-event branch from b158200 to e28b2a2 Compare August 17, 2026 10:44
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-events branch from ff14afc to b23702f Compare August 17, 2026 13:30
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-create-event branch from e28b2a2 to 76c03ac Compare August 17, 2026 13:30
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-events branch from b23702f to 5ec7c74 Compare August 19, 2026 06:14
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-create-event branch from 76c03ac to 45a5841 Compare August 19, 2026 06:14
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

UI Demo Check — a screenshot or demo is attached.

@harshtandiya
harshtandiya force-pushed the version-2-dashboard-create-event branch from 45a5841 to a42088d Compare August 19, 2026 07:20
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-team-events branch 2 times, most recently from 94f451c to 3a81380 Compare August 19, 2026 14:17
Base automatically changed from version-2-dashboard-team-events to develop August 19, 2026 14:27
harshtandiya and others added 7 commits August 19, 2026 20:01
`Event Venue`, `Event Host` and `Buzz Event` all gained a mandatory team when
multi-tenancy landed, but the records `before_tests` seeds were not updated. The
seeding runs before any test does, so `bench run-tests` has been failing for
every module rather than for one.

Seeds them with the Administrator's default team, which the same function
already creates for exactly this reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The manager shell resolves the team named in the route and 404s a team the user
does not belong to. A full-screen page has the same need but cannot be a child
of the layout that holds the logic, so the watcher moves into a composable both
can call. No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds `buzz.api.events.create_event`, the endpoint behind the dashboard's create
form. Membership decides who may use it: anything below Manager on the team is
refused, as is a non-member.

`Buzz Event` requires a category and a host that the form does not ask for, so
the service fills them in. The host is the team's own, minted on first use since
a new team has none. The category is Meetups, or Zoom Meeting when a Zoom
meeting was asked for — that one is not cosmetic, it is what marks an event as
Zoom-backed.

The Zoom meeting is booked after the event exists and linked back to it, the
same call the Desk button makes. Zoom is checked before the insert so a missing
integration refuses outright rather than half-making an event, and a failure
during the call leaves the event standing rather than rolling it back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The contour rings were dense enough to moire at card size, and at one pixel they
broke into a dotted stipple wherever a curve ran near-horizontal. Widens the
spacing and draws the rings two pixels thick, so they read as lines.

The line width becomes a constant rather than a literal repeated across both
gradient stops — the test that pins ring spacing was matching on it by accident.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One control for both kinds of location: the team's venues, searched as you type,
and a Zoom meeting under Virtual. A name nothing answers to offers to add a
venue, carrying what was typed into the dialog.

Venues read and write through `frappe.client`; Event Venue's team query
condition and permission hook are what scope them, so the team filter is only
narrowing what the picker asks for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Start and end, each a date beside a time, and a time zone picker searchable by
city, country, everyday name or zone id.

The zone data all comes from Intl, so the list tracks the browser's own tzdata
with nothing to maintain. Country needs inverting: there is no zone-to-country
lookup, only region-to-zones, so every two-letter code is swept once, on first
open rather than on mount. Zones IANA has renamed still arrive under the old
name, and a picker offering "Calcutta" to an organiser in India is wrong, so the
ones a reader would notice are mapped forward.

Dates are constrained rather than validated after the fact: the start cannot be
in the past, the end cannot precede the start, and an end that has fallen behind
follows the start rather than a multi-day span collapsing to one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reached from the Create Event button on the team pages, at
/manage/<team>/events/new. Full screen rather than inside the manager shell, so
the team comes from the route guard rather than the sidebar.

The page is the event as it will look: a banner, the title as the headline, and
the rest arranged around them. Without an image the banner draws the same
generated pattern the event cards use, seeded by the title as it is typed —
debounced, because a gradient cannot be transitioned, and blurred across the
change, because that is the only way to hide one.

An event needs a name, when it runs and where before it can be created; the
description is optional. Uploads are restricted to raster images, both in the
picker and on the file that comes back, and a rejected file now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@harshtandiya
harshtandiya force-pushed the version-2-dashboard-create-event branch from a42088d to f8b03af Compare August 19, 2026 14:33
harshtandiya and others added 3 commits August 25, 2026 18:01
Event Venue is autonamed by prompt, so a venue's docname is its human name
and therefore guessable. Nothing checked that the venue an event links to
belongs to the event's own team, and nothing downstream re-checks it: the
booking confirmation and the calendar invite both read the linked venue's
address without a permission check. A manager could name another team's
venue and publish its address.

The guard goes on Buzz Event rather than on the create endpoint, so it
covers the Desk form and the Event Template clone path too. An unstamped
venue predates the team backfill and is left alone, matching what
has_team_access does with an unstamped row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The create page only checked team membership, so a Viewer reached the full
form and lost the work to a 403 on save. The server is the authority and
already refuses anything below Manager; this just stops the form pretending
otherwise.

An alert sits under the header and every control is disabled. The pickers
take frappe-ui's own disabled prop, so the greying comes from the design
system. Three controls need more than that: the timezone combobox supplies
its own trigger, which does not inherit the disabled state; the editor is
contenteditable, so it takes editable instead; and the banner's hit area is
a div, which no disabled prop reaches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Buzz Event already refuses this on save, but the form gave no sign until
the round trip came back. Two layers now, because neither covers the other.

The end time picker takes a min of the start time, which bounds the list it
offers and reverts a typed value below it. That only applies to a single-day
event: a span running into another day may well end earlier in the day than
it began, so the bound lifts once the end date differs.

The picker's min cannot cover everything, so the check stays alongside it.
Min is inclusive, which still allows an end landing exactly on the start,
and it only constrains new input — an end time picked while the event was
multi-day is left untouched when the end date is pulled back onto the start
date. Both show the same message under the End row and hold the save.

Also renders the server's own validation errors, which arrive as `messages`
rather than a single `message` and so came through blank.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment on lines +115 to +118
venue_team = frappe.db.get_value("Event Venue", self.venue, "team")
# An unstamped venue predates the team backfill; role permissions still gate it.
if venue_team and venue_team != self.team:
frappe.throw(_("Venue {0} belongs to another team.").format(self.venue))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Unstamped venues bypass ownership

When a manager supplies the name of a legacy venue whose team is null or empty, this truthiness check accepts the venue and links it to the manager's event, causing its address to be exposed through booking details, booking confirmation, and calendar invitations.

How this was verified: The create API copies the supplied venue after authorizing only the event team, while downstream booking and calendar paths dereference the accepted venue without another ownership check.

Suggested change
venue_team = frappe.db.get_value("Event Venue", self.venue, "team")
# An unstamped venue predates the team backfill; role permissions still gate it.
if venue_team and venue_team != self.team:
frappe.throw(_("Venue {0} belongs to another team.").format(self.venue))
venue_team = frappe.db.get_value("Event Venue", self.venue, "team")
if venue_team != self.team:
frappe.throw(_("Venue {0} belongs to another team.").format(self.venue))

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: buzz/events/doctype/buzz_event/buzz_event.py
Line: 115-118

Comment:
**Unstamped venues bypass ownership**

When a manager supplies the name of a legacy venue whose `team` is null or empty, this truthiness check accepts the venue and links it to the manager's event, causing its address to be exposed through booking details, booking confirmation, and calendar invitations.

**How this was verified:** The create API copies the supplied venue after authorizing only the event team, while downstream booking and calendar paths dereference the accepted venue without another ownership check.

```suggestion
		venue_team = frappe.db.get_value("Event Venue", self.venue, "team")
		if venue_team != self.team:
			frappe.throw(_("Venue {0} belongs to another team.").format(self.venue))
```

**Knowledge Base Used:**
- [Event configuration and scheduling](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/buzz/-/docs/event-configuration.md)
- [Frappe application integration](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/buzz/-/docs/frappe-application-integration.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid catch — an unstamped venue is linkable by any team, which is the same leak narrowed to legacy rows. The justification in my comment was a bad analogy: has_team_access abstains on an unstamped row because role permissions still gate it, and nothing gates a venue link.

Not taking the suggestion in this PR, though. Dropping the truthiness check turns a data problem into a save failure — any site still holding such a row would find those events unsaveable, including edits that never touch the venue. The backfill should land first, then the guard tightens.

Tracked in #375, which also notes that buzz.patches.assign_default_team already covers Event Venue and is logged as applied, so the remaining work is mostly confirming it left nothing behind before the guard changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s a reasonable trade-off for this PR. The current check still protects already-stamped venues, while tightening it before the backfill is verified would make legacy records unsaveable—even on unrelated edits. #375 is the right follow-up: confirm assign_default_team left no unstamped venues, then change the guard to reject any venue whose team differs, including empty values.

@harshtandiya
harshtandiya merged commit 3638a1d into develop Aug 25, 2026
9 checks passed
@harshtandiya
harshtandiya deleted the version-2-dashboard-create-event branch August 25, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant